-
Notifications
You must be signed in to change notification settings - Fork 795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add method based attribute setting to encoding classes for Altair 5 #2592
Conversation
I merely think people will combine the methods as such !python -m pip install git+https://github.com/joelostblom/altair.git@method-based-attr-setting
import altair as alt
from vega_datasets import data
source = data.cars.url
s1 = alt.Scale(zero=False)
s2 = alt.Scale(scheme='viridis')
chart = alt.Chart(source).mark_point().encode(
x=alt.X('Horsepower:Q').axis(labels=False).scale(s1).title('foo'),
color=alt.Color('Origin:N', scale=s2).legend(orient='bottom')
)
chart Observe on the Personally I think this deep nested setting and getting of attributes is neither a show-stopper nor a big issue. I do not think it is common usage by normal users. What I did do in the past is when a newer version of VL can render something which cannot yet pass validation in altair that I compile the chart into a VL-dict and then change the value of the corresponding key. Something as such: ctd = chart.to_dict()
ctd['encoding']['x']['title'] = {'signal': 'bah'} But it felt like a hack and not following the zen of altair, but again, this will still works. I think the interesting outstanding issue is the part how we can solve this keyword argument completion for these methods. |
…etion parameter support
I made some progress on the completions in my latest commit! The help now shows the correct signature and docstring:
The tab completion works for the class (the suboptimal ordering is an issue with jlab/ipython, but the but not for instances of the class: I asked about this on SO, but if anyone here has ideas, please chime in. Update: The SO question is solved but I think there is a separate issue in Altair that still prevents params from showing. One last issue I am running into is that completion for the method name also only works for the class, but not for instances of the class. So typing |
Hi @joelostblom, is there anything I can do to help with this PR? I think it would be great if this syntax can be used after the next major release. |
Thank you! It would be awesome if you want to look into getting the tab completion to work properly! Both the method name and the parameters inside the method are currently not showing up in the tab completion. I gave a detailed example on exactly what works and what doesn't in my comment just above and I have not worked on it more since then. I think that is the only technical problem left to fix (then there is this potential conceptual challenge #1629 (comment) and we need to add some tests and docs, but that's later). Let me know if it is unclear and I will try to clarify. |
Sorry for my git ignorance @joelostblom... should I be able to switch between my own branches of Altair and yours here: https://github.com/joelostblom/altair/tree/method-based-attr-setting All but the basics of git tend to leave me confused... |
No worries Chris, you should be able to add to my branch if you follow the steps in this article https://tighten.com/blog/adding-commits-to-a-pull-request/. You could also do what you suggested and create a new folder and clone my fork of the repo there. Since you don't have the "maintainer" permissions on Altair, I went ahead and invited you to my fork separately, so you should have no issues pushing directly to this branch. If you run into issues with this, I am also more than happy for you to copy over these changes manually and open a new PR, I don't want you to be held up by git issues |
Thanks a lot Joel, that worked! Essentially everything related to git takes two more steps than I expect. In this case, I think I was trying to go directly to the |
Hi @joelostblom, I'm trying to get a sense for the issues you mentioned #2592 (comment) Does the following count as tab-completion working for an instance? (I didn't make any changes to the code.) I don't really see how this is substantively different from the non-working example |
Interesting example, I didn't know that would work. Maybe it has something to do with the function only being called when the cell is executed and before that tab completion can't inspect further? But it must be be possible to bypass that because tab completion is working for eg We would ideally want it working for |
Hi @joelostblom, I've browsed around a little but haven't made any real progress. I tried using the Python console, and also there On the other hand, in Jupyter Lab, One non-Altair example, in Jupyter Lab, I'm not sure what the upshot is to that, just thought I would write it down! |
Thanks again for working on this Chris! I believe that the special tab completion functionality you are referring to is inherent to IPython, so if you try in the IPython console rather than the regular one you should see similar completion to that of vs code and juputerlab (although the order of the suggestions might differ). |
Hi @joelostblom, thanks for the reply! Just to clarify, the tab-completions in the console I was talking about above are just in the regular Python console (like what I get by typing "Python" into terminal). In that Python console, I will keep looking around, but please keep me updated on any ideas! I haven't thought about this kind of issue before. |
Hi @joelostblom, do you think the tab-completion issues are related to I tried making a toy example with (as far as I can tell) similar logic and the behavior seemed similar. I put the following in a separate file named
and then tried tab-completion in Jupyter Lab: To me this feels similar to the behavior we saw above. In this toy example, if we wanted |
Hmm, one thing that seems different is that in your example |
Hi @joelostblom, I haven't had any success getting the auto-completion to work so far. Do you see any potential in taking a slightly different approach like the following? I tried adding a few sample methods to the
In my simple tests that seemed to work well. For example, the following code works (based on your code above): And auto-completion seems to behave better: It seems to me like our lives would be a lot easier if we used a syntax like If it doesn't make sense to use the same definitions for everything that is a I also wasn't sure if specifying something like Do you have any immediate comments on that type of approach? I wanted to check in before trying to work on it. @jakevdp and @mattijn I'd also be very happy to hear your thoughts. |
Hmmm I don't have a good sense of what a solution here might be, but in general I would prefer working with a
Could a way forward be to break this down to a minimal reproducible example outside of altair and try to troubleshoot it that way? We could even post it to stackoverflow and get some input. I tried this earlier but ran into a separate issue that was specific to JupyterLab and didn't have time to rework the example. I don't know if we can include the specifics of this problem while still making it small enough for SO, but it might still be a useful way forward for us to troubleshoot? I will also say that personally I believe that even if we don't have tab completion, the fact that the docstring help popup is now working properly makes this PR worth merging, but it would still be very nice to get the tab completion working as well. |
@joelostblom can you synchronise this PR with the main repo? |
Thanks for the comments! I am happy with whatever approach you think is best. I did come across this comment by Jake from almost exactly one year ago that made me wonder about alternatives to redefining I'm happy to work on producing a minimal example of the failure of tab-completion, although my initial attempts make me think that whatever we produce will be too complicated and will get downvoted on Stack Overflow ;) By the way, I don't think the
|
Done! Tests seem to be failing because of an issue installing chromedriver on the test image?
Yup, do you think it would be enough to create a version of each of the gallery examples with this new syntax and use those as tests? Or should we create some new types of tests?
I was thinking about that too, but it does indeed seem like a bigger change. Maybe it is better though because it will make it really clear when we are trying to access and attributes value versus trying to call a function?
I can confirm that this didn't work for me either. There are other parameters such as |
I re-run the tests and these are fine now based on current defined tests. Basically currently only variables that can be declared as foo = alt.Chart().mark_point().encode(
alt.X(scale={})
).to_dict()
bah = alt.Chart().mark_point().encode(
alt.X().scale()
).to_dict()
foo == bah # True But variables that only can be declared as alt.Chart().mark_point().encode(
alt.X().shorthand('field:Q')
).to_dict() Gives: TypeError: 'UndefinedType' object is not callable I would suggest to have a test suite that shows which variables work, but also which variables are (as expected) not working. with pytest.raises(TypeError):
alt.Chart().mark_point().encode(
alt.X().shorthand('field:Q')
).to_dict() Maybe it can be done in combination with I can understand why this results in a |
Hi @mattijn, I didn't totally understand the
produces the following, so I guess
|
It seems that currently the attribute setter is enabled only for properties in the encoding channel that can contain an object. Strange though, that |
I'm still a little apprehensive about having something like But I'm not at all confident in my opinion and am happy to hear that having both is good and not unusual. @mattijn Thanks for the information about which encoding channels seem to be using the property setters. I haven't wrapped my head around what's happening, but I should have some time soon to look at it. |
Yeah, I agree that it can be confusing. I am not against having something like what is suggested in #2517 (comment). Not sure how much breakage this would cause for people's workflows though. I mostly use |
… backported changes that we do not test on older versions
I pushed an update that uses absolute imports, it should work as long as you have an editable install (
I think this change should not be backported to older version. I made a change in the schema to reflect this. I think we should probably regenerate the 4.17 vegalite api with the latest versions that actually used 4.17 and then freeze that folder and have all following changes only be present in the v5 folder. |
I've been thinking more about the experiments from #2592 (comment) Does adding something like
to every class with My understanding is this general strategy is endorsed by Jedi: https://jedi.readthedocs.io/en/latest/docs/usage.html?highlight=annotations#type-hinting but it's also very possible I misunderstand, or that there is a simpler solution (or that this won't actually work when we try to implement it). One thing that seems a little strange to me is, the The hardest part seems to be deciding which methods we need (like @joelostblom and @mattijn, do you see any way to have these Not sure if that makes any sense or not! |
With documentation I merely meant explanation of this approach within the documentation. How is this different vs current approach. I don't think we should already update all the examples in the example gallery in this PR. The current codeblock of defining the I still feel still a bit hesitant regarding the non-functioning tab completion for the Edit: I just noticed you post something |
Hi @mattijn, good timing! My understanding is that the above approach gets auto-completion working (like |
So for my understanding, your potential approach will define more classes in the channel.py file. Currently the classes defined in this file are the different Encoding Channels and you are going to extend these with the Encoding Channel Options GitHub page Altair-doc. These options will be different for different encoding channels so these have to be defined using a kind of lookup in the When reading this linked to the GitHub page of these encoding channel options, I noticed that it defines the class Can we use these directly instead of defining new ones in the channel.py? And then there is also no many repetition in the
|
Yup I will add a section in the docs about this. Before releasing the next version, I think we should have a discussion of whether the method based approach should be the default (which I think sounds reasonable if we don't come across any drawbacks), but for now I will write the docs page as if it isn't.
I think it is a good idea to do all the conversions now just so that we can see that the entire test suite is passing with the new syntax, and we are not overlooking anything. We don't need to merge them in this PR (or could merge them into a separate
I hear you regarding the custom part, it definitely has a "home-made" feeling to it. But to some extent I think this entire idea of this property setter is quite custom and that type of logic is not available anywhere else in the library either. For the docstring, we are mostly just propagating the properties of the actual class to the property setter wrapper, but in the last commits I also add some custom logic to make those docstrings even more informative, which I think is the part that looks the most home-made right now. I am open to other solutions here, maybe @ChristopherDavisUCI 's solution for tab completion will take care of this too so that we don't need to fiddle this much with the docstring? |
Ah yes. I forgot that the example gallery also functions as test suite. But the side effect is that the enhancement in this PR now becomes the main approach to specify Altair charts. |
6b44a4a
to
e88500e
Compare
e88500e
to
f323915
Compare
I agree that this is something we should discuss (although I think it is probably a good idea since the new syntax is more convenient to both type and read). We should probably make a new |
Good. For me personally, I'm in favour of the method based approach if the tab completion works. Let's wait patiently until the succesful findings of Chris. No pressure;). @ChristopherDavisUCI: I mentioned this before:
But I think I was wrong here isn't it? You need classes of the variables within these classes if I understand correctly? |
Thanks @mattijn! It does seem like things might be able to be streamlined a little by adding to the |
A note that there is a small inconsistency in that this new method based syntax does not apply to the |
Hi @joelostblom, am I right that it does still work if you use |
That does work! Then I think we should probably add it to |
Hi @joelostblom and @mattijn, I just wanted to say it might be another week before I'm able to get back to looking at this. Just wanted to pass along that quick update. |
Hi @joelostblom and @mattijn, do you know what produces the I've stared at this code for a few minutes: |
I think I found two approaches. Here for eg the class channel options of
import altair as alt
from altair.sphinxext.schematable import type_description
schema = alt.PositionFieldDef.resolve_references()
for prop, propschema in schema.get("properties", {}).items():
print(prop, type_description(propschema)) aggregate :class:`Aggregate`
axis anyOf(:class:`Axis`, `null`)
bandPosition `number`
bin anyOf(`boolean`, :class:`BinParams`, `string`, `null`)
field :class:`Field`
impute anyOf(:class:`ImputeParams`, `null`)
scale anyOf(:class:`Scale`, `null`)
sort :class:`Sort`
stack anyOf(:class:`StackOffset`, `null`, `boolean`)
timeUnit anyOf(:class:`TimeUnit`, :class:`TimeUnitParams`)
title anyOf(:class:`Text`, `null`)
type :class:`StandardType`
from tools.schemapi import utils
import altair as alt
si = utils.SchemaInfo(alt.PositionFieldDef)
for p in si.properties:
print(p, si.properties[p].medium_description) aggregate anyOf(:class:`NonArgAggregateOp`, :class:`ArgmaxDef`, :class:`ArgminDef`)
axis anyOf(:class:`Axis`, None)
bandPosition float
bin anyOf(boolean, :class:`BinParams`, string, None)
field anyOf(:class:`FieldName`, :class:`RepeatRef`)
impute anyOf(:class:`ImputeParams`, None)
scale anyOf(:class:`Scale`, None)
sort anyOf(:class:`SortArray`, :class:`AllSortString`, :class:`EncodingSortField`, :class:`SortByEncoding`, None)
stack anyOf(:class:`StackOffset`, None, boolean)
timeUnit anyOf(:class:`TimeUnit`, :class:`TimeUnitParams`)
title anyOf(:class:`Text`, None)
type enum('quantitative', 'ordinal', 'temporal', 'nominal') |
I made a new branch adding some type hints in #2769 (I didn't want to make edits to this branch directly because I am sure there are some issues in what I've produced). I'm very happy for any comments! |
Implemented in #2795 |
* Aliases for ImputeParams and TitleParams Let's not merge this for a little while to make sure this doesn't break anything and to think about whether we need to do anything more sophisticated. The point is to make it easier for `impute` and `title` to find the appropriate docstrings, as discussed in #2592 * Update altair/vegalite/v5/api.py Co-authored-by: Joel Ostblom <joelostblom@users.noreply.github.com> * Remove StackOffset alias * Add a test related to aliases Trying to ensure that we are not overwriting something defined on the Vega-Lite side when we define `Bin`, `Impute`, and `Title` in Altair. * Update tests/vegalite/v5/tests/test_alias.py Co-authored-by: Joel Ostblom <joelostblom@users.noreply.github.com> * Update tests/vegalite/v5/tests/test_alias.py Co-authored-by: Mattijn van Hoek <mattijn@gmail.com> Co-authored-by: Joel Ostblom <joelostblom@users.noreply.github.com> Co-authored-by: Mattijn van Hoek <mattijn@gmail.com>
I have been busy lately and not made progress beyond moving Jake's changes from #1629 into the right places of the current development branch. They seem to still be working fine from my local testing, but I will not have much time to work on this until June/July so anyone who wants to take over before then please do! I would love to see this syntax in Altair!
I think the biggest outstanding item is the one mention in #1629 (comment). So either figuring out a workaround for that or making progress on these smaller outstanding changes would be helpful?
Working example:
Maybe it's nicer to format it like this: